package com.xunk.scene.service;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.google.common.collect.Lists;
import com.xunk.common.core.domain.ControlDeviceParam;
import com.xunk.common.core.domain.EquipmentInfoVO;
import com.xunk.common.core.exception.CustomException;
import com.xunk.common.core.text.Convert;
import com.xunk.common.core.utils.BeanUtil;
import com.xunk.common.core.utils.StringUtils;
import com.xunk.common.core.utils.serialization.JsonSerializer;
import com.xunk.common.core.web.domain.AjaxResult;
import com.xunk.common.redis.service.RedisService;
import com.xunk.common.security.utils.SecurityUtils;
import com.xunk.equipment.api.RemoteEquipmentService;
import com.xunk.scene.config.StorageConfig;
import com.xunk.scene.constant.SceneConstant;
import com.xunk.scene.domain.*;
import com.xunk.scene.enums.CompareOperatorEnum;
import com.xunk.scene.enums.ExecutionResultEnum;
import com.xunk.scene.enums.RepeatTypeEnum;
import com.xunk.scene.handler.ConditionBranch;
import com.xunk.scene.handler.HandlerClient;
import com.xunk.scene.handler.IConditionHandler;
import com.xunk.scene.handler.delay.DelayTaskQueueDaemonThread;
import com.xunk.scene.handler.delay.SceneDelayActionEquipment;
import com.xunk.scene.handler.effectiveperiod.EffectivePeriodLeaf;
import com.xunk.scene.handler.equipment.ConditionEquipmentLeaf;
import com.xunk.scene.handler.weather.ConditionWeatherLeaf;
import com.xunk.scene.mapper.*;
import com.xunk.scene.util.CronUtil;
import com.xunk.scene.util.ValidationUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * @ProjectName: HIM
 * @Package: com.xunk.scene.service
 * @ClassName: SceneService
 * @Author: luanzhiwei
 * @Description: 场景服务层
 * @Date: 2021/5/6 18:57
 * @Version: 1.0
 */
@Service
@Slf4j
public class SceneService {
    private final ReentrantLock mainLock = new ReentrantLock();


    @Autowired
    private SceneResultActionMapper sceneResultActionMapper;
    @Autowired
    private SceneConditionEffectivePeriodMapper effectivePeriodMapper;
    @Autowired
    private SceneActionEquipmentMapper sceneActionEquipmentMapper;
    @Autowired
    private SceneConditionEquipmentMapper sceneConditionEquipmentMapper;
    @Autowired
    private SceneActionMsgMapper sceneActionMsgMapper;
    @Autowired
    private SceneMapper sceneMapper;
    @Autowired
    private SceneResultExecMapper sceneResultExecMapper;
    @Autowired
    private ConditionEquipmentComponent conditionEquipmentComponent;
    @Autowired
    private HomeUserMapper homeUserMapper;
    @Autowired
    private SceneConditionWeatherMapper sceneConditionWeatherMapper;
    @Autowired
    private XxlJobService xxlJobService;

    @Autowired
    private DeviceService deviceService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private FastFileStorageClient fileStorageClient;
    @Autowired
    private StorageConfig storageConfig;
    @Autowired
    private SceneIconMapper sceneIconMapper;
    @Autowired
    private DnakeEdgeSceneIdGenMapper dnakeMapper;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private SceneEdgeMapper edgeMapper;

    @Autowired
    private XxlJobMapper xxlJobMapper;
    @Autowired
    private RemoteEquipmentService remoteEquipmentService;

    /**
     * 添加场景
     *
     * @param params
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult addScene(Map<String, Object> params) throws Exception {
        StringBuffer remark = new StringBuffer();
        String userId = Convert.toStr(SecurityUtils.getUserId());
        AddOrUpdateScene addOrUpdateScene = JsonSerializer.deserialize(JsonSerializer.serialize(params), AddOrUpdateScene.class);
        //是否模板 1 是， 0否
        final String isModel = addOrUpdateScene.getIsModel();
        Scene scene = addOrUpdateScene.extractScene();
        scene.setCreateBy(userId);
        scene.setUpdateBy(userId);
        if (StringUtils.equals(isModel, SceneConstant.STRING_ONE)) {
            //设置模板
            ValidationUtils.validate(scene, Scene.Model.class);
        } else {
            ValidationUtils.validate(scene, Scene.Add.class);
            Long homeId = addOrUpdateScene.getHomeId();
            List<Long> homeIds = getHomeIds(Convert.toLong(userId));
            if (!homeIds.contains(homeId)) {
                log.info("当前userid：{}与homeid：{}不存在绑定关系，添加场景失败", userId, homeId);
                return AjaxResult.error("不存在绑定关系，不允许添加场景");
            }
        }
        if (!addOrUpdateScene.isPeriodOk()) {
            return AjaxResult.error("选择生效时间段时，请至少添加一个条件");
        }
        //插入场景表
        int insertScenes = sceneMapper.insertScene(scene);
        if (insertScenes != 1) {
            log.info("新增场景失败");
            return AjaxResult.error();
        }
        //获取场景id，用于插入其他表赋值
        Long scenesId = scene.getId();
        //插入设备条件表
        List<SceneConditionEquipment> sceneConditionEquipment = addOrUpdateScene.getConditionEquipments();
        if (!CollectionUtils.isEmpty(sceneConditionEquipment)) {
            for (SceneConditionEquipment condition : sceneConditionEquipment) {
                if (BeanUtil.isEmptyBean(condition)) {
                    continue;
                }
                condition.setSceneId(scenesId);
                condition.setCreateBy(userId);
                condition.setUpdateBy(userId);
                if (StringUtils.equals(isModel, SceneConstant.STRING_ONE)) {
                    ValidationUtils.validate(condition, SceneConditionEquipment.Model.class);
                } else {
                    ValidationUtils.validate(condition, SceneConditionEquipment.Add.class);
                }
                sceneConditionEquipmentMapper.insertEquipmentCondition(condition);
            }
        }
        //插入生效时段条件表
        SceneConditionEffectivePeriod periodCondition = addOrUpdateScene.getConditionEffectivePeriod();
        if (BeanUtil.isEmptyBean(periodCondition)) {
            periodCondition = new SceneConditionEffectivePeriod();
            //默认生效时段为全天，每天
            periodCondition.setStartTime("00:00");
            periodCondition.setStopTime("00:00");
            periodCondition.setEffectivePeriod("00:00-00:00");
        } else {
            //拼接描述语句
            final String description = RepeatTypeEnum.getDescription(periodCondition.getRepeatType(), periodCondition.getSpecificDay());
            if (StringUtils.isNotEmpty(description)) {
                remark.append(description).append(",");
            }
        }
        periodCondition.setCreateBy(userId);
        periodCondition.setSceneId(scenesId);
        periodCondition.setUpdateBy(userId);
        if (StringUtils.equals(isModel, SceneConstant.STRING_ONE)) {
            ValidationUtils.validate(periodCondition, SceneConditionEffectivePeriod.Model.class);
        } else {
            ValidationUtils.validate(periodCondition, SceneConditionEffectivePeriod.Add.class);
        }
        effectivePeriodMapper.insertEffectivePeriodCondition(periodCondition);

        /**
         *添加定时任务到xxlJob
         *
         * 如果执行为空 并且触发条件为天气触发时，添加一个默认的每间隔10分钟执行的定时任务执行此场景
         */

        List<TaskSchedule> taskSchedules = addOrUpdateScene.getTaskSchedules();
        if (!CollectionUtils.isEmpty(taskSchedules)) {
            if (BeanUtil.isNotEmptyBean(periodCondition)) {
                remark.append(scene.getRelationTypeDesc());
            }
            for (TaskSchedule taskSchedule :
                    taskSchedules) {
                xxlJobService.addCronToXxl(taskSchedule, scenesId, false);
                remark.append(CronUtil.buildCronDescription(taskSchedule)).append("且");
            }
            remark.deleteCharAt(remark.length() - 1);
        }
        addOrUpdateScene.setId(scenesId);

        //插入天气条件表
        List<SceneConditionWeather> sceneConditionWeatherList = addOrUpdateScene.getConditionWeathers();
        if (!CollectionUtils.isEmpty(sceneConditionWeatherList)) {
            if (!CollectionUtils.isEmpty(taskSchedules)) {
                remark.append(scene.getRelationTypeDesc());
            }
            for (SceneConditionWeather sceneConditionWeather :
                    sceneConditionWeatherList) {
                //如场景只含天气条件，标注此天气条件为唯一条件
                if (CollectionUtils.isEmpty(sceneConditionEquipment) && CollectionUtils.isEmpty(taskSchedules))
                    sceneConditionWeather.setIsOnly(1);

                if (BeanUtil.isEmptyBean(sceneConditionWeather))
                    continue;
                sceneConditionWeather.setSceneId(scenesId);
                sceneConditionWeather.setCreateBy(userId);
                sceneConditionWeather.setUpdateBy(userId);
                if (StringUtils.equals(isModel, SceneConstant.STRING_ONE)) {
                    ValidationUtils.validate(sceneConditionWeather, SceneConditionWeather.Model.class);
                } else {
                    ValidationUtils.validate(sceneConditionWeather, SceneConditionWeather.Add.class);
                }
                sceneConditionWeatherMapper.insertConditionWeather(sceneConditionWeather);
                remark.append(sceneConditionWeather.getWeatherDescription())
                        .append(CompareOperatorEnum.getDescByCode(sceneConditionWeather.getCompare()))
                        .append(sceneConditionWeather.getPropValue());
            }
        }

        //插入设备动作表
        List<SceneActionEquipment> sceneActionEquipment = addOrUpdateScene.getActionEquipments();
        if (!CollectionUtils.isEmpty(sceneActionEquipment)) {
            //如果是边缘设备，存储边缘场景
//            final List<SceneActionEquipment> dnake = sceneActionEquipment.stream().filter(item -> item.getDeviceCategory().equals(SceneConstant.STRING_TWO) && item.getSupplier().equals("dnake")).collect(Collectors.toList());
            //狄耐克边缘设备单独作为场景落库
//            addEdgeScene(dnake);
            remark.append("将");
            for (SceneActionEquipment action : sceneActionEquipment) {
                //设备全部落库，但边缘设备不以设备执行，以场景维度执行
                if (BeanUtil.isEmptyBean(action)) {
                    continue;
                }
                action.setSceneId(scenesId);
                action.setCreateBy(userId);
                action.setUpdateBy(userId);
                if (StringUtils.equals(isModel, SceneConstant.STRING_ONE)) {
                    ValidationUtils.validate(action, SceneActionEquipment.Model.class);
                } else {
                    ValidationUtils.validate(action, SceneActionEquipment.Add.class);
                }
                sceneActionEquipmentMapper.insertEquipmentAction(action);
                remark.append(action.getRoomName())
                        .append("的")
                        .append(action.getDeviceName().replace(action.getRoomName(), ""))
                        .append(action.getPropDescription())
                        .append(action.getValueDescription()).append(",");
            }
            remark.deleteCharAt(remark.length() - 1);
        }

        //插入消息动作表
        SceneActionMsg sceneActionMsg = addOrUpdateScene.getActionMsg();
        if (BeanUtil.isNotEmptyBean(sceneActionMsg)) {
            sceneActionMsg.setSceneId(scenesId);
            sceneActionMsg.setCreateBy(userId);
            sceneActionMsg.setUpdateBy(userId);
            if (StringUtils.equals(isModel, SceneConstant.STRING_ONE)) {
                ValidationUtils.validate(sceneActionMsg, SceneActionMsg.Model.class);
            } else {
                ValidationUtils.validate(sceneActionMsg, SceneActionMsg.Add.class);
            }
            sceneActionMsgMapper.insertMessageAction(sceneActionMsg);
        }

        //更新备注
        Scene updateScene = new Scene();
        updateScene.setId(scenesId);
        updateScene.setRemark(remark.toString());
        sceneMapper.updateScene(updateScene);

        return AjaxResult.success(addOrUpdateScene);
    }

    /**
     * 编辑场景
     *
     * @param params
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult editScene(Map<String, Object> params) throws Exception {
        AddOrUpdateScene addOrUpdateScene = JsonSerializer.deserialize(JsonSerializer.serialize(params), AddOrUpdateScene.class);
        final Long oldId = addOrUpdateScene.getId();
        sceneMapper.deleteCascadeById(oldId);
        final AjaxResult addScene = this.addScene(params);
        final AddOrUpdateScene deserialize = JsonSerializer.deserialize(JsonSerializer.serialize(addScene.getData()), AddOrUpdateScene.class);
        final Long sceneId = deserialize.getId();
        sceneResultExecMapper.updateSceneId(oldId, sceneId);
        return addScene;
    }

    /**
     * 查询家庭id下全部场景概览
     *
     * @param homeId
     * @param appFlag  1 app，0 pad
     * @param homePage 首页标识 1是0否，该字段传1查首页,并且不区分手动和自动列表。该字段传0或不传查自定义,区分手动和自动列表
     * @return
     */
    public AjaxResult getScenes(Long homeId, String appFlag, Long homePage) {
        String flag = StringUtils.EMPTY;
        boolean isHomePage = false;
        Long userId = SecurityUtils.getUserId();
        List<Long> homeIdsByUserId = getHomeIds(userId);
        if (!homeIdsByUserId.contains(homeId)) {
            log.info("当前userid：{}与homeid：{}不存在绑定关系，修改场景失败", userId, homeId);
            return AjaxResult.error("不存在绑定关系，不允许查看场景");
        }
        if (ObjectUtil.equals(SceneConstant.STRING_ONE, Convert.toStr(homePage))) {
            isHomePage = true;
            flag = Convert.toStr(homePage);
        }
        List<Scene> scenes = sceneMapper.selectSceneByHomeId(homeId, flag, appFlag);
        if (isHomePage) {
            return AjaxResult.success(scenes);
        } else {
            final List<Scene> manual = scenes.stream().filter(item -> item.getExecType().equals(SceneConstant.STRING_ONE)).sorted(Comparator.comparing(Scene::getCreateTime).reversed()).collect(Collectors.toList());
            final List<Scene> auto = scenes.stream().filter(item -> item.getExecType().equals(SceneConstant.STRING_ZERO)).sorted(Comparator.comparing(Scene::getCreateTime).reversed()).collect(Collectors.toList());
            Map<String, Object> data = new HashMap<>();
            data.put("manual", manual);
            data.put("auto", auto);
            return AjaxResult.success(data);
        }
    }

    /**
     * 根据场景id，查询场景详细内容
     *
     * @param sceneId
     * @return
     */
    public AjaxResult getSceneDetail(Long sceneId, String appFlag) {
        final Long userId = SecurityUtils.getUserId();
        final List<Long> homeIdsByUserId = getHomeIds(userId);
        final Long homeId = sceneMapper.selectHomeIdById(sceneId);
        if (!homeIdsByUserId.contains(homeId)) {
            log.info("当前场景：{}与用户：{}所在家庭：{}不存在绑定关系", sceneId, userId, homeId);
            return AjaxResult.error("不存在绑定关系，不允许查看");
        }
        final AddOrUpdateScene addOrUpdateScene = new AddOrUpdateScene();
        final Scene scene = sceneMapper.selectSceneById(sceneId, appFlag);
        addOrUpdateScene.setScene(scene);
        //查询生效时段条件
        SceneConditionEffectivePeriod periodCondition = effectivePeriodMapper.selectEffectivePeriodConditionBySceneId(sceneId);
        addOrUpdateScene.setConditionEffectivePeriod(periodCondition);
        //查询设备条件
        List<SceneConditionEquipment> sceneConditionEquipments = sceneConditionEquipmentMapper.selectEquipmentConditionBySceneId(sceneId);
        addOrUpdateScene.setConditionEquipments(sceneConditionEquipments);
        //查询天气条件
        List<SceneConditionWeather> sceneConditionWeathers = sceneConditionWeatherMapper.queryWeatherConditionDetailBySceneId(sceneId);
        addOrUpdateScene.setConditionWeathers(sceneConditionWeathers);
        //查询设备动作
        List<SceneActionEquipment> sceneActionEquipments = sceneActionEquipmentMapper.selectEquipmentActionBySceneId(sceneId);
        addOrUpdateScene.setActionEquipments(sceneActionEquipments);
        //查询消息动作
        SceneActionMsg sceneActionMsg = sceneActionMsgMapper.selectMessageActionBySceneId(sceneId);
        addOrUpdateScene.setActionMsg(sceneActionMsg);

        //查询定时任务
        List<TaskSchedule> taskSchedules = xxlJobService.getTaskSchedule(sceneId);
        if (!taskSchedules.isEmpty()) {
            addOrUpdateScene.setTaskSchedules(taskSchedules);
        }

        return AjaxResult.success(addOrUpdateScene);
    }

    /**
     * 逻辑删除场景
     *
     * @param sceneId
     * @param homeId
     * @return
     */
    public AjaxResult delScene(long sceneId, Long homeId) {
        final int count = sceneMapper.selectCountByHomeIdAndSceneId(homeId, sceneId);
        if (count < 1) {
            log.info("当前家庭：{}与场景：{}不存在绑定关系", homeId, sceneId);
            return AjaxResult.error("家庭与场景不存在绑定关系，不允许删除");
        }
        final Long userIdLong = SecurityUtils.getUserId();
        final List<Long> homeIds = getHomeIds(userIdLong);
        if (!homeIds.contains(homeId)) {
            log.info("用户{}所在家庭{}与场景{}不存在绑定关系", userIdLong, homeId, sceneId);
            return AjaxResult.error("用户所在家庭与场景不存在绑定关系，不允许删除");
        }

        String userId = Convert.toStr(userIdLong);
        sceneMapper.hideSceneByIdAndHomeId(sceneId, homeId, userId);
        List<SceneConditionEquipment> old = sceneConditionEquipmentMapper.selectEquipmentConditionBySceneId(sceneId);
        sceneConditionEquipmentMapper.hideEquipmentConditionBySceneId(sceneId, userId);
        //删除缓存中当前场景下的设备条件数据
        Set<String> remove = old.stream().map(item -> item.getDeviceSn() + SceneConstant.SEPARATOR_EQUIPMENT + item.getDeviceProp()).collect(Collectors.toSet());
        conditionEquipmentComponent.removeFromRedis(remove);
        effectivePeriodMapper.hideEffectivePeriodConditionBySceneId(sceneId, userId);
        sceneActionEquipmentMapper.hideEquipmentActionById(sceneId, userId);
        sceneActionMsgMapper.hideMessageActionBySceneId(sceneId, userId);
        sceneConditionWeatherMapper.hideWeatherConditionBySceneId(sceneId, userId);

        //暂停场景下的定时任务
        xxlJobService.startOrStop(sceneId, "0");


        return AjaxResult.success();
    }

    /**
     * 执行场景动作,记录执行日志
     *
     * @param id         场景id
     * @param redisValue redis中存储的设备id+设备属性
     */
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult execSceneAction(String id, String redisValue) {
        SceneResultExec sceneResultExec = new SceneResultExec();
        Long sceneId;
        if (StringUtils.isEmpty(id)) {
            String[] split = redisValue.split(SceneConstant.SEPARATOR_EQUIPMENT);
            String deviceSn = split[0];
            sceneId = sceneConditionEquipmentMapper.selectSceneIdByDeviceSn(deviceSn);
        } else {
            sceneId = Convert.toLong(id);
        }


        Long userId;
        //调度中心发起的定时任务不携带userId，此时手动设置执行任务的userID为999999
        try {
            userId = SecurityUtils.getUserId();
        } catch (NullPointerException e) {
            userId = new Long(999999);
        }
        //判断是否为调度中心发起
        boolean isXxlUser = userId.compareTo(new Long(999999)) == 0 ? true : false;

        final List<Long> homeIds = getHomeIds(userId);
        final Long homeId = sceneMapper.selectHomeIdById(sceneId);
        if (!isXxlUser && !homeIds.contains(homeId)) {
            log.info("当前用户：{}所在家庭：{}与场景：{}不存在绑定关系", userId, homeId, sceneId);
            return AjaxResult.error("用户所在家庭与场景不存在绑定关系");
        }
        final Scene scene = sceneMapper.selectSceneById(sceneId, StringUtils.EMPTY);
        sceneResultExec.setTriggerId(StringUtils.isEmpty(redisValue) ? "手动点击" : redisValue);
        sceneResultExec.setSceneId(sceneId);
        sceneResultExec.setSceneName(scene.getSceneName());
        sceneResultExec.setHomeId(homeId);
        sceneResultExec.setResult(ExecutionResultEnum.EXECUTING.getCode());
        sceneResultExec.setCreateBy(Convert.toStr(userId));
        sceneResultExec.setUpdateBy(Convert.toStr(userId));
        //向场景执行总记录表中插入一条数据
        sceneResultExecMapper.insertSceneResult(sceneResultExec);
        Long resultId = sceneResultExec.getId();
        return this.execSceneAction(sceneId, resultId, false);
    }

    /**
     * 执行场景动作
     *
     * @param sceneId  场景id
     * @param resultId 场景执行记录表id
     * @return
     */
    private AjaxResult execSceneAction(long sceneId, long resultId, boolean isCron) {
        log.info(String.format("开始执行场景：%d动作", sceneId));
        Long userId;
        if (isCron) {
            userId = new Long(999999);
        } else {
            userId = SecurityUtils.getUserId();
        }
        //获取设备动作
        //TODO 查询非狄耐克网关子设备
        final SceneActionEquipment sceneActionEquipment = new SceneActionEquipment();
        sceneActionEquipment.setSceneId(sceneId);
        sceneActionEquipment.setActiveFlag("1");
        List<SceneActionEquipment> actions = sceneActionEquipmentMapper.selectEquipmentActionList(sceneActionEquipment);
        //TODO 查询狄耐克边缘场景并执行
//        final Long sceneEdgeId = edgeMapper.selectIdBySceneId(sceneId);

        //定义设备操作类-立刻执行的
        List<ControlDeviceParam> controlList = new ArrayList<>();

        if (actions.isEmpty()) {
            throw new CustomException(String.format("场景%s无可执行动作", sceneId));
        }

        //立刻执行的动作
        final List<SceneActionEquipment> immediately = actions.stream().filter(item -> item.getDelay() == 0).collect(Collectors.toList());
        //延时执行的动作
        final List<SceneActionEquipment> delay = actions.stream().filter(item -> item.getDelay() != 0).collect(Collectors.toList());
        //添加到延时队列里
        if (!CollectionUtils.isEmpty(delay)) {
            for (SceneActionEquipment delayAction : delay) {
                DelayTaskQueueDaemonThread.getInstance().put(delayAction.getDelay(), TimeUnit.SECONDS, new SceneDelayActionEquipment(delayAction));
            }
        }
        //如果立即执行的动作列表为空，返回处理成功
        if (CollectionUtils.isEmpty(immediately)) {
            log.info("不存在立即执行的动作");
            return AjaxResult.success();
        }
        //调用设备控制
        //过滤全部开关类型的动作
        final List<SceneActionEquipment> switchList = immediately.stream().filter(item -> SceneConstant.STRING_ONE.equals(item.getIsSwitch())).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(switchList)) {
            switchList.stream().forEach((action) -> controlList.add(new ControlDeviceParam(null, action.getDeviceSn(), action.getPropValue(), action.getDeviceProp(), null)));
            AjaxResult switchRes = deviceService.controlDevice(controlList);
            //插入执行日志表
            CompletableFuture.runAsync(() -> handleExecResult(switchRes, actions, resultId, userId));
            if (!switchRes.isSuccess()) {
                return switchRes;
            }
        }

        //拼接非开关类型的立即执行列表
        final List<SceneActionEquipment> otherList = immediately.stream().filter(item -> (!SceneConstant.STRING_ONE.equals(item.getIsSwitch()))).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(otherList)) {
            otherList.stream().forEach((action) -> controlList.add(new ControlDeviceParam(null, action.getDeviceSn(), action.getPropValue(), action.getDeviceProp(), null)));
            AjaxResult immediatelyRes = deviceService.controlDevice(controlList);
            //插入执行日志表
            CompletableFuture.runAsync(() -> handleExecResult(immediatelyRes, actions, resultId, userId));
            return immediatelyRes;
        }
        log.info(String.format("场景：%d动作执行完毕", sceneId));
        return AjaxResult.success();
    }

    /**
     * 执行场景动作,定时执行调用此方法执行
     *
     * @param id         场景id
     * @param redisValue redis中存储的设备id+设备属性
     */
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult execSceneAction(String id, String redisValue, boolean isCron) {
        SceneResultExec sceneResultExec = new SceneResultExec();
        Long sceneId;
        if (StringUtils.isEmpty(id)) {
            String[] split = redisValue.split(SceneConstant.SEPARATOR_EQUIPMENT);
            String deviceSn = split[0];
            sceneId = sceneConditionEquipmentMapper.selectSceneIdByDeviceSn(deviceSn);
        } else {
            sceneId = Convert.toLong(id);
        }
        Long userId;
        //调度中心发起的定时任务不携带userId，此时手动设置执行任务的userID为999999
        try {
            userId = SecurityUtils.getUserId();
        } catch (NullPointerException e) {
            userId = new Long(999999);
        }

        final Scene scene = sceneMapper.selectSceneById(sceneId, StringUtils.EMPTY);
        sceneResultExec.setTriggerId(StringUtils.isEmpty(redisValue) ? "手动点击" : redisValue);
        sceneResultExec.setSceneId(sceneId);
        sceneResultExec.setSceneName(scene.getSceneName());
        sceneResultExec.setHomeId(new Long(999999));  //默认999999为定时任务执行
        sceneResultExec.setResult(ExecutionResultEnum.EXECUTING.getCode());
        sceneResultExec.setCreateBy(Convert.toStr(userId));
        sceneResultExec.setUpdateBy(Convert.toStr(userId));
        //向场景执行总记录表中插入一条数据
        sceneResultExecMapper.insertSceneResult(sceneResultExec);
        Long resultId = sceneResultExec.getId();
        return this.execSceneAction(sceneId, resultId, true);
    }

    /**
     * 记录执行结果
     *
     * @param result
     * @param actions
     * @param resultId
     * @param userId
     */
    private void handleExecResult(AjaxResult result, List<SceneActionEquipment> actions, Long resultId, Long userId) {
        String StringUserId;
        //调度中心发起的定时任务不携带userId，此时手动设置执行任务的userID为999999
        try {
            StringUserId = Convert.toStr(userId);
        } catch (NullPointerException e) {
            StringUserId = "999999";
        }
        final SceneResultExec sceneResultExec = new SceneResultExec();
        sceneResultExec.setId(resultId);
        sceneResultExec.setFailReason(result.getMsg());
        if (result.isSuccess()) {
            //执行成功，循环更新为成功
            sceneResultExec.setResult(ExecutionResultEnum.SUCCESS.getCode());
            for (SceneActionEquipment action : actions) {
                SceneResultAction resultAction = new SceneResultAction();
                resultAction.setSceneResultId(resultId);
                resultAction.setDeviceSn(action.getDeviceSn());
                resultAction.setExpectValue(action.getPropValue());
                resultAction.setCreateBy(StringUserId);
                resultAction.setUpdateBy(StringUserId);
                //执行成功，默认赋值当前值为设置值
                resultAction.setCurrentValue(action.getPropValue());
                resultAction.setResult(ExecutionResultEnum.SUCCESS.getCode());
                sceneResultActionMapper.insertActionResult(resultAction);
            }
        } else {
            //执行失败，更新失败数据
            sceneResultExec.setResult(ExecutionResultEnum.FAIL.getCode());
            final List<ControlDeviceParam> data = (List<ControlDeviceParam>) result.getData();

            for (SceneActionEquipment action : actions) {
                SceneResultAction resultAction = new SceneResultAction();
                resultAction.setSceneResultId(resultId);
                resultAction.setDeviceSn(action.getDeviceSn());
                resultAction.setExpectValue(action.getPropValue());
                resultAction.setCreateBy(StringUserId);
                resultAction.setUpdateBy(StringUserId);
                boolean failFlag = false;
                for (ControlDeviceParam datum : data) {
                    if (datum.getDeviceSn().equals(action.getDeviceSn())) {
                        failFlag = true;
                        resultAction.setFailReason(result.getMsg());
                        resultAction.setResult(ExecutionResultEnum.FAIL.getCode());

                    }
                }
                if (failFlag) {
                    //如果失败
                    resultAction.setResult(ExecutionResultEnum.FAIL.getCode());
                    resultAction.setFailReason(result.getMsg());
                } else {
                    //执行成功，默认赋值当前值为设置值
                    resultAction.setCurrentValue(action.getPropValue());
                    resultAction.setResult(ExecutionResultEnum.SUCCESS.getCode());
                }
                sceneResultActionMapper.insertActionResult(resultAction);
            }
        }
        sceneResultExecMapper.updateSceneResult(sceneResultExec);

    }

    /**
     * 设备状态变更，触发匹配条件，如有新增条件类型，都在该方法中添加
     *
     * @param notifyEntity
     * @return
     * @throws Exception
     */
    public boolean matchCondition(DeviceNotifyEntity notifyEntity) throws Exception {
        boolean matchResult = false;
        if (!conditionEquipmentComponent.exist(notifyEntity.getRedisValue())) {
            return false;
        }
        String deviceSn = notifyEntity.getDeviceSn();
        String deviceProp = notifyEntity.getType();
        long sceneId = sceneConditionEquipmentMapper.selectSceneIdByDeviceSn(deviceSn);
        Scene scene = sceneMapper.selectSceneById(sceneId, StringUtils.EMPTY);
        if (scene.getActiveFlag().equals(SceneConstant.SCENE_ACTIVE_N)) {
            log.info("场景未开启，不触发条件");
            return false;
        }
        ConditionBranch root = new ConditionBranch();
        //查询场景下生效时间段条件
        SceneConditionEffectivePeriod effectivePeriod = effectivePeriodMapper.selectEffectivePeriodConditionBySceneId(sceneId);
        EffectivePeriodLeaf effectivePeriodLeaf = new EffectivePeriodLeaf();
        IConditionHandler periodWrapper = effectivePeriodLeaf.wrap(effectivePeriod);
        root.add(periodWrapper);
        //查询场景下天气条件
        List<SceneConditionWeather> sceneConditionWeathers = sceneConditionWeatherMapper.queryWeatherConditionBySceneId(sceneId);
        ConditionWeatherLeaf conditionWeatherLeaf = new ConditionWeatherLeaf();
        List<IConditionHandler> weatherWrapper = conditionWeatherLeaf.wrap(sceneConditionWeathers);
        root.addAll(weatherWrapper);
        //查询该场景下全部有效的设备条件
        SceneConditionEquipment conditionEquipment = new SceneConditionEquipment();
        conditionEquipment.setDelFlag(SceneConstant.DEL_N);
        conditionEquipment.setSceneId(sceneId);
        List<SceneConditionEquipment> conditions = sceneConditionEquipmentMapper.selectEquipmentConditionList(conditionEquipment);
        //过滤掉触发条件
        List<SceneConditionEquipment> collect = conditions.stream().filter(item -> (item.getDeviceSn().equals(deviceSn) && item.getDeviceProp().equals(deviceProp))).collect(Collectors.toList());
        ConditionEquipmentLeaf leaf = new ConditionEquipmentLeaf();
        List<IConditionHandler> equipmentWrapper = leaf.wrap(collect);
        root.addAll(equipmentWrapper);
        //执行条件匹配
        HandlerClient handlerClient = new HandlerClient(root);
        //获取条件关系类型进行条件条件判断
        if (StringUtils.isEmpty(scene.getRelationType())) {
            log.error("条件关系类型为空，无法进行条件判断");
            return matchResult;
        }
        if (scene.getRelationType().equals("1")) {
            matchResult = handlerClient.allMatch();
        } else {
            matchResult = handlerClient.oneMatch();
        }
        log.info("条件匹配结果{}", matchResult);

        return matchResult;
    }

    /**
     * 根据场景ID判断条件是否满足
     *
     * @param sceneId
     * @return
     * @throws Exception
     */
    public boolean matchCondition(long sceneId) throws Exception {
        boolean matchResult;
        Scene scene = sceneMapper.selectSceneById(sceneId, StringUtils.EMPTY);
        if (scene.getActiveFlag().equals(SceneConstant.SCENE_ACTIVE_N)) {
            log.info("场景未开启，不触发条件");
            return false;
        }
        log.info("条件间关系：{},(1且0或)", scene.getRelationType());
        ConditionBranch root = new ConditionBranch();
        //查询场景下生效时间段条件，该条件必须满足
        SceneConditionEffectivePeriod effectivePeriod = effectivePeriodMapper.selectEffectivePeriodConditionBySceneId(sceneId);
        EffectivePeriodLeaf effectivePeriodLeaf = new EffectivePeriodLeaf();
        IConditionHandler periodWrapper = effectivePeriodLeaf.wrap(effectivePeriod);
        if (!periodWrapper.match(false)) {
            log.info("场景生效时间段不满足条件");
            return false;
        }
        //查询场景下天气条件
        List<SceneConditionWeather> sceneConditionWeathers = sceneConditionWeatherMapper.queryWeatherConditionBySceneId(sceneId);
        ConditionWeatherLeaf conditionWeatherLeaf = new ConditionWeatherLeaf();
        List<IConditionHandler> weatherWrapper = conditionWeatherLeaf.wrap(sceneConditionWeathers);
        root.addAll(weatherWrapper);
        //查询该场景下全部有效的设备条件
        SceneConditionEquipment conditionEquipment = new SceneConditionEquipment();
        conditionEquipment.setDelFlag(SceneConstant.DEL_N);
        conditionEquipment.setSceneId(sceneId);
        List<SceneConditionEquipment> conditions = sceneConditionEquipmentMapper.selectEquipmentConditionList(conditionEquipment);
        ConditionEquipmentLeaf leaf = new ConditionEquipmentLeaf();
        List<IConditionHandler> equipmentWrapper = leaf.wrap(conditions);
        root.addAll(equipmentWrapper);
        //执行条件匹配
        HandlerClient handlerClient = new HandlerClient(root);

        //获取条件关系类型进行条件条件判断
        if (StringUtils.isEmpty(scene.getRelationType()))
            log.error("条件关系类型为空，无法进行条件判断");
        if (scene.getRelationType().equals("1")) {
            matchResult = handlerClient.allMatch();
        } else {
            matchResult = handlerClient.oneMatch();
        }
        log.info("条件匹配结果{}", matchResult);

        return matchResult;
    }

    /**
     * 添加场景到我的家庭
     *
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult addToMyHome(Map params) throws Exception {
        AddOrUpdateScene addOrUpdateScene = JsonSerializer.deserialize(JsonSerializer.serialize(params), AddOrUpdateScene.class);
        addOrUpdateScene.setIsModel("0");
        //此处derive不确定到底是不是模板的场景id
        Long derive = addOrUpdateScene.getId();
        //此处sceneId最终赋值要是已存在场景的id，或不存在的话，赋值为null
        Long sceneId = derive;
        Scene scene = addOrUpdateScene.extractScene();
        ValidationUtils.validate(scene, Scene.Add.class);
        final Long homeId = scene.getHomeId();
        //以模板id查询，如果存在，表明是模板id，则再去查该家庭下是否有该场景模板
        Scene sceneById = sceneMapper.selectSceneById(derive, StringUtils.EMPTY);
        boolean isModelId = false;
        if (sceneById.getIsModel().equals(SceneConstant.STRING_ONE)) {
            //是模板id
            isModelId = true;
        }
        boolean newFlag = false;
        //如果是模板id
        if (isModelId) {
            //赋值code和备注，用于APP端展示
            scene.setSceneCode(sceneById.getSceneCode());
            scene.setRemark(sceneById.getRemark());
            //查询derive字段，是否存在数据，存在则证明已经保存过了，是要更新数据
            final Scene myScene = sceneMapper.selectSceneByHomeIdAndDerive(homeId, derive, StringUtils.EMPTY);
            if (BeanUtil.isEmptyBean(myScene)) {
                //如果查不到表示需要新添加
                sceneId = null;
                scene.setDerive(derive);
                newFlag = true;
            } else {
                //如果能查到表示已存在场景
                sceneId = myScene.getId();
            }
        }
        Long userId = SecurityUtils.getUserId();
        List<Long> homeIds = getHomeIds(userId);
        if (!homeIds.contains(homeId)) {
            log.info("当前userid：{}与homeid：{}不存在绑定关系，修改场景失败", userId, homeId);
            return AjaxResult.error("用户与家庭不存在绑定关系，不允许修改场景");
        }
        if (!newFlag) {
            scene.setId(sceneId);
            //非新增，修改
            int count = sceneMapper.selectCountByHomeIdAndSceneId(homeId, sceneId);
            if (count < 1) {
                log.info("当前homeId：{}与sceneId：{}不存在绑定关系，修改场景失败", homeId, sceneId);
                return AjaxResult.error("不存在绑定关系，不允许修改场景");
            }
            sceneMapper.updateScene(scene);
        } else {
            final int insertScene = sceneMapper.insertScene(scene);
            if (insertScene != 1) {
                log.info("新增场景失败");
                return AjaxResult.error();
            }
            sceneId = scene.getId();
        }
        //更新生效时间段条件
        final SceneConditionEffectivePeriod effectivePeriod = addOrUpdateScene.getConditionEffectivePeriod();
        if (!ObjectUtils.isEmpty(effectivePeriod)) {
            effectivePeriod.setSceneId(sceneId);
            effectivePeriodMapper.hideEffectivePeriodConditionBySceneId(sceneId, Convert.toStr(userId));
            effectivePeriodMapper.insertEffectivePeriodCondition(effectivePeriod);
        }
        //更新天气条件,先删除旧的天气再添加
        List<SceneConditionWeather> sceneConditionWeathers = addOrUpdateScene.getConditionWeathers();
        if (!CollectionUtils.isEmpty(sceneConditionWeathers)) {
            sceneConditionWeatherMapper.deleteWeatherConditionBySceneId(sceneId);
            for (SceneConditionWeather condition :
                    sceneConditionWeathers) {
                if (BeanUtil.isEmptyBean(condition))
                    continue;
                condition.setSceneId(sceneId);
                condition.setCreateBy(String.valueOf(userId));
                condition.setUpdateBy(String.valueOf(userId));
                ValidationUtils.validate(condition, SceneConditionWeather.Add.class);
                sceneConditionWeatherMapper.insertConditionWeather(condition);
            }
        }

        //更新设备条件
        List<SceneConditionEquipment> sceneConditionEquipment = addOrUpdateScene.getConditionEquipments();
        List<SceneConditionEquipment> old = sceneConditionEquipmentMapper.selectEquipmentConditionBySceneId(sceneId);
        Set<String> remove = old.stream().map(item -> item.getDeviceSn() + SceneConstant.SEPARATOR_EQUIPMENT + item.getDeviceProp()).collect(Collectors.toSet());
        //删除缓存中当前场景下的设备条件数据
        conditionEquipmentComponent.removeFromRedis(remove);
        //删除之前的条件，插入新的条件
        sceneConditionEquipmentMapper.hideEquipmentConditionBySceneId(sceneId, Convert.toStr(userId));
        if (!CollectionUtils.isEmpty(sceneConditionEquipment)) {
            for (SceneConditionEquipment condition : sceneConditionEquipment) {
                if (BeanUtil.isEmptyBean(condition)) {
                    continue;
                }
                condition.setSceneId(sceneId);
                condition.setCreateBy(Convert.toStr(userId));
                condition.setUpdateBy(Convert.toStr(userId));
                ValidationUtils.validate(condition, SceneConditionEquipment.Add.class);
                sceneConditionEquipmentMapper.insertEquipmentCondition(condition);
            }
        }

        List<SceneConditionEquipment> add = sceneConditionEquipmentMapper.selectEquipmentConditionBySceneId(sceneId);
        Set<String> current = add.stream().map(item -> item.getDeviceSn() + SceneConstant.SEPARATOR_EQUIPMENT + item.getDeviceProp()).collect(Collectors.toSet());
        //添加当前场景下的最新设备条件数据
        conditionEquipmentComponent.addToRedis(current);
        //更新设备执行动作
        List<SceneActionEquipment> sceneActionEquipment = addOrUpdateScene.getActionEquipments();
        //删除旧数据，添加新数据
        sceneActionEquipmentMapper.hideEquipmentActionById(sceneId, Convert.toStr(userId));
        if (!CollectionUtils.isEmpty(sceneActionEquipment)) {
            //如果是边缘设备，存储边缘场景
            final List<SceneActionEquipment> dnake = sceneActionEquipment.stream().filter(item -> item.getDeviceCategory().equals(SceneConstant.STRING_TWO) && item.getSupplier().equals("dnake")).collect(Collectors.toList());
            //狄耐克边缘设备单独作为场景落库
            addEdgeScene(dnake);
            for (SceneActionEquipment action : sceneActionEquipment) {
                //设备全部落库，但边缘设备不以设备执行，以场景维度执行
                if (BeanUtil.isEmptyBean(action)) {
                    continue;
                }
                action.setSceneId(sceneId);
                action.setCreateBy(Convert.toStr(userId));
                action.setUpdateBy(Convert.toStr(userId));
                ValidationUtils.validate(action, SceneActionEquipment.Add.class);
                sceneActionEquipmentMapper.insertEquipmentAction(action);
            }
        }

        //更新发送消息动作
        SceneActionMsg sceneActionMsg = addOrUpdateScene.getActionMsg();
        if (BeanUtil.isNotEmptyBean(sceneActionMsg)) {
            sceneActionMsgMapper.updateMessageAction(sceneActionMsg);
        }

        //编辑时先删除原场景的定时任务再新增
        List<TaskSchedule> taskSchedules = addOrUpdateScene.getTaskSchedules();
        //删除场景的定时任务，不必判断返回结果
        xxlJobService.removeBySceneId(scene.getId());
        if (!CollectionUtils.isEmpty(taskSchedules)) {
            for (TaskSchedule taskSchedule :
                    taskSchedules) {
                xxlJobService.addCronToXxl(taskSchedule, scene.getId(), false);
            }
        } else if (!CollectionUtils.isEmpty(sceneConditionWeathers)) {
            TaskSchedule taskSchedule = new TaskSchedule("DAY", "*/10", "*");
            xxlJobService.addCronToXxl(taskSchedule, scene.getId(), true);
        }
        Map<String, Object> result = new HashMap<>();
        result.put("id", sceneId);
        return AjaxResult.success(result);
    }

    /**
     * 查询全部模板场景
     *
     * @return
     */
    public AjaxResult getModels() {
        List<Scene> maps = sceneMapper.selectModelScene();
        return AjaxResult.success(maps);
    }

    /**
     * 查询模板详情
     *
     * @param sceneId 模板id
     * @param homeId  家庭id
     * @param appFlag APP标识
     * @return
     */
    public AjaxResult getModelDetail(Long sceneId, Long homeId, String appFlag) {
        Long derive = sceneId;
        Scene scene;
        //按照模板场景id跟家庭查询，查不到证明是模板
        final Scene MyScene = sceneMapper.selectSceneByHomeIdAndDerive(homeId, derive, appFlag);
        boolean isModel = true;
        if (!ObjectUtil.isEmpty(MyScene)) {
            isModel = false;
            scene = MyScene;
            //含有derive
            derive = MyScene.getDerive();
            sceneId = MyScene.getId();
        } else {
            //非模板
            //derive=sceneId
            scene = sceneMapper.selectSceneById(derive, appFlag);
        }
        if (BeanUtil.isEmptyBean(scene)) {
            return AjaxResult.error("模板作者已关闭或删除模板");
        }
        final AddOrUpdateScene addOrUpdateScene = new AddOrUpdateScene();
        addOrUpdateScene.setId(sceneId);
        addOrUpdateScene.setSceneName(scene.getSceneName());
        addOrUpdateScene.setSceneCode(scene.getSceneCode());
        addOrUpdateScene.setHomePage(scene.getHomePage());
        addOrUpdateScene.setProfileUrl(scene.getProfileUrl());
        addOrUpdateScene.setHomeId(homeId);
        addOrUpdateScene.setDelFlag(scene.getDelFlag());
        addOrUpdateScene.setActiveFlag(scene.getActiveFlag());
        addOrUpdateScene.setRelationType(scene.getRelationType());
        addOrUpdateScene.setExecType(scene.getExecType());
        addOrUpdateScene.setDerive(derive);
        addOrUpdateScene.setIsModel(SceneConstant.STRING_ZERO);
        addOrUpdateScene.setRemark(scene.getRemark());

        //查询生效时段条件
        SceneConditionEffectivePeriod periodCondition = effectivePeriodMapper.selectEffectivePeriodConditionBySceneId(derive);
        addOrUpdateScene.setConditionEffectivePeriod(periodCondition);
        //查询设备条件
        List<SceneConditionEquipment> sceneConditionEquipments = sceneConditionEquipmentMapper.selectEquipmentConditionBySceneId(derive);
        addOrUpdateScene.setConditionEquipments(sceneConditionEquipments);
        //查询天气条件
        List<SceneConditionWeather> sceneConditionWeathers = sceneConditionWeatherMapper.queryWeatherConditionBySceneId(derive);
        addOrUpdateScene.setConditionWeathers(sceneConditionWeathers);
        List<SceneActionEquipment> sceneActionEquipments = new ArrayList<>();
        //查询设备动作
        if (!isModel) {
            //如果之前存过，那么删除，且重新加载
            sceneActionEquipmentMapper.hideEquipmentActionById(sceneId, Convert.toStr(SecurityUtils.getUserId()));
        }
        final List<SceneActionEquipment> selectModel = sceneActionEquipmentMapper.selectModel(derive);
        //查询配置的设备动作
        final List<SceneActionEquipment> collect = selectModel.stream().map(item -> {
            item.setActiveFlag(SceneConstant.STRING_ZERO);
            return item;
        }).collect(Collectors.toList());
        // 查询设备列表接口，进行赋值。
        final AjaxResult list = remoteEquipmentService.list(homeId, 0);
        List<EquipmentInfoVO> homeEquipments = JsonSerializer.deserializeArray(JsonSerializer.serialize(list.getData()), EquipmentInfoVO.class);
        if (!CollectionUtils.isEmpty(homeEquipments)) {
            //循环预制设备动作
            for (SceneActionEquipment actionEquipment : collect) {
                //循环家庭全部设备
                for (EquipmentInfoVO equipmentInfoVO : homeEquipments) {
                    final String deviceType = equipmentInfoVO.getDeviceType();
                    final String actionEquipmentDeviceType = actionEquipment.getDeviceType();
                    if (StringUtils.equals(deviceType, actionEquipmentDeviceType)) {
                        SceneActionEquipment item = ObjectUtil.clone(actionEquipment);
                        item.setDeviceSn(equipmentInfoVO.getDeviceSn());
                        item.setActiveFlag(SceneConstant.STRING_ONE);
                        item.setDeviceName(equipmentInfoVO.getDeviceName());
                        item.setSupplier(equipmentInfoVO.getSupplier());
                        item.setRoomName(equipmentInfoVO.getRoomName());
                        sceneActionEquipments.add(item);
                    }
                }
            }
        } else {
            sceneActionEquipments = collect;
        }

        addOrUpdateScene.setActionEquipments(sceneActionEquipments);
        //查询消息动作
        SceneActionMsg sceneActionMsg = sceneActionMsgMapper.selectMessageActionBySceneId(derive);
        addOrUpdateScene.setActionMsg(sceneActionMsg);

        //查询定时任务
        List<TaskSchedule> taskSchedules = xxlJobService.getTaskSchedule(derive);
        if (!taskSchedules.isEmpty()) {
            addOrUpdateScene.setTaskSchedules(taskSchedules);
        }

        return AjaxResult.success(addOrUpdateScene);
    }


    /**
     * 修改场景开启/关闭
     *
     * @param homeId
     * @param sceneId
     * @param activeFlag
     * @return
     */
    public boolean changeStatus(Long homeId, Long sceneId, String activeFlag) {
        //校验越权
        final int count = sceneMapper.selectCountByHomeIdAndSceneId(homeId, sceneId);
        if (count < 1) {
            log.info("当前家庭：{}与场景：{}不存在绑定关系", homeId, sceneId);
            throw new CustomException("家庭与场景不存在绑定关系，不允许修改");
        }
        final Long userId = SecurityUtils.getUserId();
        List<Long> homeIds = getHomeIds(userId);
        if (!homeIds.contains(homeId)) {
            log.info("当前userid：{}与homeid：{}不存在绑定关系，操作失败", userId, homeId);
            throw new CustomException("不存在绑定关系，不允许修改");
        }
        int i = sceneMapper.updateActiveFlag(homeId, sceneId, activeFlag);
        xxlJobService.startOrStop(sceneId, activeFlag);
        return i == 1 ? true : false;
    }

    /**
     * 根据userId查询所属家庭列表
     *
     * @param userId
     * @return
     */
    public List<Long> getHomeIds(long userId) {
        String key = SceneConstant.HOME_ID_LIST + userId;
        List<Long> cacheList = JsonSerializer.deserializeArray(redisService.getCacheObject(key), Long.class);
        if (CollectionUtils.isEmpty(cacheList)) {
            //从redis中未查到该userid对应的homeid，累计1分钟内，每次都从数据库查，都查不到，超过10次，则赋值uuid，防止一直查数据库
            String missKey = SceneConstant.MISS_HOME_ID_LIST_COUNT + userId;
            final Long increment = redisService.redisTemplate.opsForValue().increment(missKey);
            redisService.expire(missKey, 1L, TimeUnit.MINUTES);
            if (increment > 10) {
                return Collections.singletonList(RandomUtil.randomLong());
            }
            cacheList = homeUserMapper.findHomeIdsByUserId(userId);
            if (!CollectionUtils.isEmpty(cacheList)) {
                //从数据库中能查到homeid，缓存10分钟
                redisService.setCacheObject(key, JsonSerializer.serialize(cacheList), 1, TimeUnit.DAYS);
            }
        }
        return cacheList;
    }

    /**
     * 查询当前场景全部执行记录
     *
     * @param homeId
     * @param sceneId
     * @param pageIndex
     * @return
     */
    public AjaxResult getExecLog(Long homeId, Long sceneId, Long pageIndex) {
        Long userId = SecurityUtils.getUserId();
        List<Long> homeIdsByUserId = getHomeIds(userId);
        if (!homeIdsByUserId.contains(homeId)) {
            log.info("当前userid：{}与homeid：{}不存在绑定关系，不允许查看", userId, homeId);
            return AjaxResult.error("不存在绑定关系，不允许查看执行记录");
        }
        final SceneResultExec sceneResultExec = new SceneResultExec();
        sceneResultExec.setHomeId(homeId);
        sceneResultExec.setSceneId(sceneId);
        Map<String, Object> result = new HashMap<>();
        //该场景全部执行结果概览
        //yyyy-mm-dd 格式的，按照日期分组倒序排列
        final List<String> dates = sceneResultExecMapper.selectSceneResultPaging(homeId);
        if (CollectionUtils.isEmpty(dates)) {
            result.put("content", new ArrayList<>());
            return AjaxResult.success(result);
        }
        //按10条分页
        final List<List<String>> partition = Lists.partition(dates, 10);
        int size = partition.size();
        if (Convert.toInt(pageIndex) > size) {
            result.put("content", new ArrayList<>());
            return AjaxResult.success(result);
        }
        //获取当前页
        final List<String> dateList = partition.get(Convert.toInt(pageIndex - 1));
        final String startDate = dateList.get(0);
        final String endDate = dateList.get(size - 1);
        final List<PeriodSceneResultEntity> periodSceneResult = sceneResultExecMapper.selectPeriodSceneResult(homeId, startDate, endDate);
        List<Map<String, Object>> resultList = new ArrayList<>();
        result.put("pageSize", partition.size());
        for (int i = 0; i < size; i++) {
            final String date = dateList.get(i);
            Map<String, Object> each = new HashMap<>();
            final List<PeriodSceneResultEntity> collect = periodSceneResult.stream().filter(item -> item.getShowDate().equals(date)).collect(Collectors.toList());
            //更新未终态的数据
            for (PeriodSceneResultEntity entity : collect) {
                final SceneResultAction sceneResultAction = new SceneResultAction();
                sceneResultAction.setSceneResultId(entity.getId());
                //如果当前场景还未最终结束，查询
                if (entity.getResult().equals(ExecutionResultEnum.EXECUTING.getCode())) {
                    final List<SceneResultAction> sceneResultActions = sceneResultActionMapper.selectActionResultList(sceneResultAction);
                    //找到不成功的条目
                    final Optional<SceneResultAction> first = sceneResultActions.stream().filter(item -> item.getResult().equals(ExecutionResultEnum.FAIL.getCode()) || !item.valueEqual()).findFirst();
                    SceneResultExec resultExec = new SceneResultExec();
                    if (first.isPresent()) {
                        resultExec.setResult(ExecutionResultEnum.FAIL.getCode());
                    } else {
                        resultExec.setResult(ExecutionResultEnum.SUCCESS.getCode());
                    }
                    resultExec.setId(entity.getId());
                    //更新result
                    sceneResultExecMapper.updateSceneResult(resultExec);
                }
            }
            each.put("date", date);
            each.put("collect", collect);
            resultList.add(each);
        }
        result.put("content", resultList);
        return AjaxResult.success(result);
    }

    /**
     * 删除场景执行记录
     *
     * @param homeId
     * @return
     */
    public AjaxResult delExecLog(Long homeId) {
        Long userId = SecurityUtils.getUserId();
        List<Long> homeIdsByUserId = getHomeIds(userId);
        final List<Long> list = sceneResultExecMapper.selectIdListByHomeId(homeId);
        if (!homeIdsByUserId.contains(homeId)) {
            log.info("当前userid：{}与homeid：{}不存在绑定关系，不允许删除", userId, homeId);
            return AjaxResult.error("不存在绑定关系，不允许删除");
        }
        if (CollectionUtils.isEmpty(list)) {
            return AjaxResult.success();
        }
        for (Long item : list) {
            sceneResultActionMapper.deleteBySceneResultId(item);
        }
        sceneResultExecMapper.deleteSceneResultByIds(list.stream().mapToLong(Long::valueOf).toArray());
        return AjaxResult.success();
    }

    /**
     * 上传场景头像
     *
     * @param name       模板图像显示名称
     * @param unselected 未选中图标、用户头像
     * @param selected   选中图标
     * @param sceneId
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult uploadSceneIcon(String name, MultipartFile unselected, MultipartFile selected, Long sceneId) {
        if (ObjectUtils.isEmpty(unselected)) {
            throw new CustomException("上传文件为空");
        }
        final Long userId = SecurityUtils.getUserId();
        //未选中图标路径
        StorePath unselectedPath;
        //已选中图标路径
        StorePath selectedPath;
        String selectedFullPath = StringUtils.EMPTY;
        if (!ObjectUtils.isEmpty(sceneId)) {
            //如果是自定义场景头像更换，校验越权
            List<Long> homeIds = getHomeIds(userId);
            final Long homeId = sceneMapper.selectHomeIdById(sceneId);
            if (!homeIds.contains(homeId)) {
                log.info("当前userid：{}与homeid：{}不存在绑定关系，添加头像失败", userId, homeId);
                return AjaxResult.error("不存在绑定关系，不允许添加头像");
            }
        } else {
            //模板头像
            if (StringUtils.isEmpty(name)) {
                log.info("头像名称为空");
                throw new CustomException("头像名称不能为空");
            }
            if (Objects.isNull(selected)) {
                log.info("上传文件为空");
                throw new CustomException("上传文件为空");
            }
            //上传选中时头像
            try (final InputStream inputStream = selected.getInputStream()) {
                selectedPath = fileStorageClient.uploadFile(inputStream, selected.getSize(),
                        getFileExtension(selected), null);
            } catch (IOException e) {
                log.error(e.getMessage());
                throw new CustomException("场景图片上传失败");
            }
            selectedFullPath = storageConfig.getDomain() + "/" + selectedPath.getFullPath();
        }
        //上传未选中时头像/用户头像
        try (final InputStream inputStream = unselected.getInputStream()) {
            unselectedPath = fileStorageClient.uploadFile(inputStream, unselected.getSize(),
                    getFileExtension(unselected), null);
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new CustomException("场景图片上传失败");
        }
        final String unselectedFullPath = storageConfig.getDomain() + "/" + unselectedPath.getFullPath();
        if (!ObjectUtils.isEmpty(sceneId)) {
            //用户头像
            final Scene scene = new Scene();
            scene.setId(sceneId);
            scene.setProfileUrl(unselectedFullPath);
            sceneMapper.updateScene(scene);
        } else {
            //插入模板头像
            final SceneIcon sceneIcon = new SceneIcon();
            sceneIcon.setIconName(name);
            sceneIcon.setSelectedPath(selectedFullPath);
            sceneIcon.setUnselectedPath(unselectedFullPath);
            sceneIcon.setCreateBy(Convert.toStr(userId));
            sceneIcon.setUpdateBy(Convert.toStr(userId));
            sceneIconMapper.insertSceneIcon(sceneIcon);
        }
        return AjaxResult.success();
    }


    /**
     * 获取文件后缀格式
     *
     * @param file
     * @return
     */
    private String getFileExtension(MultipartFile file) {
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        String suffix = "bmp,jpg,png,tif,gif,pcx,tga,exif,fpx,svg,psd,cdr,pcd,dxf,ufo,eps,ai,raw,WMF,webp,avif";
        if (!StringUtils.containsIgnoreCase(suffix, extension)) {
            log.info("上传附件格式为：{}", extension);
            throw new CustomException("不允许上传非图片格式文件");
        }
        return StringUtils.isEmpty(extension) ? Objects.requireNonNull(file.getContentType()).substring(file.getContentType().indexOf("/") + 1) : extension;
    }

    /**
     * 查询全部场景模板头像
     *
     * @param appFlag
     * @return
     */
    public AjaxResult getSceneIcon(String appFlag) {
        List<SceneIcon> sceneIcons;
        if (StringUtils.isNotEmpty(appFlag) && StringUtils.equals(appFlag, SceneConstant.STRING_ONE)) {
            //如果不为空且等于1 ，则查app图标
            final Object app = redisService.getCacheObject(SceneConstant.APP_ICON_LIST);
            if (ObjectUtils.isEmpty(app)) {
                sceneIcons = sceneIconMapper.selectAppIcons();
                redisService.setCacheObject(SceneConstant.APP_ICON_LIST, sceneIcons, 10, TimeUnit.MINUTES);
                return AjaxResult.success(sceneIcons);
            }
            return AjaxResult.success(JsonSerializer.deserializeArray(JsonSerializer.serialize(app), SceneIcon.class));
        } else {
            //其他情况查pad端图标
            final Object pad = redisService.getCacheObject(SceneConstant.PAD_ICON_LIST);
            if (ObjectUtils.isEmpty(pad)) {
                sceneIcons = sceneIconMapper.selectPadIcons();
                redisService.setCacheObject(SceneConstant.PAD_ICON_LIST, sceneIcons, 10, TimeUnit.MINUTES);
                return AjaxResult.success(sceneIcons);
            }
            return AjaxResult.success(JsonSerializer.deserializeArray(JsonSerializer.serialize(pad), SceneIcon.class));
        }
    }

    /**
     * 添加头像到我的家庭
     *
     * @param id
     * @param sceneId
     * @return
     */
    public AjaxResult addSceneIconToMyScene(Long id, Long sceneId) {
        //校验越权
        final Long userId = SecurityUtils.getUserId();
        List<Long> homeIds = getHomeIds(userId);
        final Long homeId = sceneMapper.selectHomeIdById(sceneId);
        if (!homeIds.contains(homeId)) {
            log.info("当前userid：{}与homeid：{}不存在绑定关系，添加头像失败", userId, homeId);
            return AjaxResult.error("不存在绑定关系，不允许添加头像");
        }
        final SceneIcon sceneIcon = sceneIconMapper.selectByid(id);
        sceneMapper.addSceneIconToMyScene(sceneId, sceneIcon.getUnselectedPath());
        return AjaxResult.success();
    }

    /**
     * 添加到首页
     *
     * @param homeId
     * @param sceneId
     * @param homePage
     * @return
     */
    public AjaxResult changeHomePage(Long homeId, Long sceneId, String homePage) throws Exception {
        //校验越权
        final int count = sceneMapper.selectCountByHomeIdAndSceneId(homeId, sceneId);
        if (count < 1) {
            log.info("当前家庭：{}与场景：{}不存在绑定关系", homeId, sceneId);
            return AjaxResult.error("家庭与场景不存在绑定关系，不允许添加到首页");
        }
        final Long userId = SecurityUtils.getUserId();
        List<Long> homeIds = getHomeIds(userId);
        if (!homeIds.contains(homeId)) {
            log.info("当前userid：{}与homeid：{}不存在绑定关系，操作失败", userId, homeId);
            return AjaxResult.error("不存在绑定关系，不允许添加到首页");
        }
        sceneMapper.updateHomePageById(sceneId, homePage);
        return AjaxResult.success();
    }

    /**
     * 添加边缘场景
     *
     * @param dnake
     */
    @Transactional(rollbackFor = Exception.class)
    public void addEdgeScene(List<SceneActionEquipment> dnake) throws Exception {
        if (CollectionUtils.isEmpty(dnake)) {
            return;
        }
        final Optional<SceneActionEquipment> first = dnake.stream().filter(item -> StringUtils.isNotEmpty(item.getDeviceSn())).findFirst();
        if (!first.isPresent()) {
            log.error("获取狄耐克网关子设备的设备号异常");
            throw new CustomException("获取网关设备号异常");
        }
        final SceneActionEquipment sceneActionEquipment = first.get();
        final String parentSn = deviceMapper.selectParentSnByDeviceSn(sceneActionEquipment.getDeviceSn());
        if (StringUtils.isEmpty(parentSn)) {
            log.error("根据狄耐克网关子设备获取绑定的狄耐克网关的设备号异常");
            throw new CustomException("获取网关设备号异常");
        }
        final Long edgeSceneId = getAndIncreaseEdgeSceneId(parentSn);
        //添加边缘场景 TODO
        //xxx.setScene(parentSn, edgeSceneId, List<DnakeLinkageData> dataList)

    }

    /**
     * 根据网关设备号获取该网关对应的最新边缘场景id
     *
     * @param deviceSn 网关设备号
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Long getAndIncreaseEdgeSceneId(String deviceSn) throws Exception {
        String uuid = IdUtil.randomUUID();
        String key = SceneConstant.EDGE_SCENE_ID_GEN + deviceSn;
        redisService.tryLock(key, uuid, 10 * 60, true);
        final ReentrantLock getLock = this.mainLock;
        getLock.lock();
        try {
            final Long edgeSceneId = dnakeMapper.selectEdgeSceneIdByDnameDeviceSn(deviceSn);
            if (ObjectUtils.isEmpty(edgeSceneId)) {
                Long now = 101L;
                final DnakeEdgeSceneIdGen gen = new DnakeEdgeSceneIdGen();
                gen.setEdgeSceneId(now);
                gen.setDnakeDeviceSn(deviceSn);
                gen.setCreateBy(SecurityUtils.getUsername() + SecurityUtils.getUserId());
                dnakeMapper.insert(gen);
                return now;
            } else {
                Long now = edgeSceneId + 1;
                dnakeMapper.update(deviceSn, now);
                return now;
            }
        } catch (Exception e) {
            log.error("获取最新边缘场景id异常,设备号：{},异常信息：{}", deviceSn, e.getMessage());
            throw new CustomException("获取最新边缘场景id异常");
        } finally {
            getLock.unlock();
            redisService.unlock(key, uuid);
        }
    }

    /**
     * 立即执行场景时，处理模板场景id，返回真正的场景id
     * 如果不是模板场景id，则返回null
     * 如果是模板场景id，首先查询该家庭下是否有该模板场景，
     * 如果没有，先生成一条新的数据，返回新数据的场景id，用于执行
     *
     * @param sceneId
     * @param homeId
     * @return
     */
    public Long getRealSceneId(Long sceneId, Long homeId, String appFlag) throws Exception {
        Scene sceneById = sceneMapper.selectSceneById(sceneId, appFlag);
        boolean isModelId = false;
        if (sceneById.getIsModel().equals(SceneConstant.STRING_ONE)) {
            //是模板id
            isModelId = true;
        }
        //如果是模板id
        if (isModelId) {
            //查询derive字段，是否存在数据
            final Scene myScene = sceneMapper.selectSceneByHomeIdAndDerive(homeId, sceneId, appFlag);
            if (BeanUtil.isEmptyBean(myScene)) {
                //如果查不到表示需要新添加
                final AjaxResult modelDetail = this.getModelDetail(sceneId, homeId, appFlag);
                final Map<String, Object> deserializeMap = JsonSerializer.deserializeMap(JsonSerializer.serialize(modelDetail.getData()));
                final AjaxResult ajaxResult = this.addToMyHome(deserializeMap);
                final Map<String, Object> addResult = JsonSerializer.deserializeMap(JsonSerializer.serialize(ajaxResult.getData()));
                return Convert.toLong(addResult.get("id"));
            } else {
                //如果能查到表示已存在场景，直接返回sceneid
                return myScene.getId();
            }
        } else {
            return null;
        }
    }
}
